iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Engineering

從單一agent 到多agent 集群的開發流水帳以及應用系列 第 6

Day 05(上):一天,八個階段,29 個格子 — 從安裝到住進 mesh 的終端

  • 分享至 

  • xImage
  •  

系列背景:我在用 Rust 打造私有、自持的 AI agent 網格 spectyn-mesh——
單一 binary、CLI / TUI / App / HTTP 多介面、本地與雲端模型混編、多機聯邦。
Day 04 收掉了「不准猜」。今天的題目是操作者早上出的:

「spectyn cli 的全生命週期的完整運作,從安裝到運作,經過不同 ai
測試可行
後,然後開始重構 app 版的介面,分成 spectyn 跟 mesh 介面,
spectyn cli 要能在 mesh 介面裡以 terminal ui 的方式運作,然後透過
ensemble 帶領其他 ai(也可以是其他 ai 帶領,像是 claude code)
——
總之,完整的重構 spectyn cli 以及 ensemble。」

後來又追加一條:「還要測試在多個使用場景下能不能跟 opencode 跟
codex 一樣正常運作,沒有的話要補上跟再測試
。」

這篇是那一天的整理版。收官答案:八個階段、29 個格子,全數勾選;
42 個 commit;三條測試線從紅到零。
但比帳面更值得寫的,是過程中
我猜錯了幾次、誤判了幾次、破了自己的規矩一次——那些才是這篇的骨頭。

scorecard

一、先把目標變成可勾選的東西

一段話的目標不能執行。第一件事是把它拆成 BIG-GOAL + 階段化
checkbox 佇列
,存成 plan/day05-goal-refactor.md,再讓 /loop
指著這個檔案逐項推進——每完成一項就勾一格、記一段、commit 一次。

拆之前先派了四路唯讀偵察兵(ensemble 現況 / app 結構 / terminal
內嵌可行性 / 生命週期缺口),因為佇列要基於事實,不能基於印象
偵察回來的第一個驚喜:「ensemble」在這個 repo 裡是三個不同的東西;
第二個驚喜:app 的三頁殼已經上線了,mesh 終端的 PTY 基建也早就在
原本以為的大工程,有一半是接線工程。

裁定點也在這時寫死:「完整重構」= 契約收斂而非砍掉重寫
(重寫會丟掉四天的驗證資產);LSP、codex 全套 session 生命週期、
遠端 dispatch = 今天不做,誠實列出

二、R0 全生命週期:四站從沒被驗過

lifecycle

生命週期六站,前兩站有測試,後四站——常駐、升級、解除安裝——
零自動化驗證
。挖下去每一站都有東西:

宣告債 15 條。 我原本只想修「update 宣告了但不存在」。母規則的
作法是先造一個從 binary 自身取清單的 ratchet(印出宣告表、逐條打
--help,單源不漂移)——它一紅,紅出 15 條:update/version
宣告多年沒有任何處理常式;六條把求助當未知動作;七條 usage
有印但 exit 2(求助是問題,不是用法錯誤)。

service install 壞得比想像徹底。 讀實作時撞見:代碼替換的佔位符是
__SPECTYN_BIN__,而模板裡寫的是 __PHANTOM_BIN__——替換根本
沒發生;加上模板 Label 還是改名前的舊值。合起來:現在跑
service install,會裝出一個「指向字面 PHANTOM_BIN、掛在舊
Label 下」的服務——既起不來,也被 status / uninstall 永遠找不到。

而且四個模板全是滯留戶。修完在隔離埠跑全循環首驗:
install→running→status→uninstall,現役服務未受一根指頭。

self-update 五情境沙盒補上(happy path 原子換、壞 sha 拒裝、
sidecar 404 拒裝、純 http 連下載都不發生、dry-run 零副作用),
母規則紅示範:把 sha 驗證打瞎 → 情境應聲而紅 → 恢復 → 全綠。
附帶抓到改名債第四案:self-update 重啟探測只認新 Label,
而操作者現役服務是舊名裝的
——更新成功後 daemon 會無聲留在舊 binary。

三、R1/R2:讓四家 AI 當使用者,再跟兩家對標

寫了十條使用者劇本(裝、問、改檔核准、fork、@file、失效轉移、
管線、退出碼、錯誤訊息品質、打錯字),派 codex / agy / opencode
各自在沙盒裡真的操作 spectyn,我自己走一遍當第四家。

判詞裡最重的一條三家同時中:repl -c 在零設定下死在
「anthropic no key」,而同一台機器的 exec 會自動 fallback 到本機
ollama
——同一個使用者、同一個沙盒,兩個入口兩種行為。而且那句
死路訊息還在 macOS 上教 PowerShell 語法。修法不是複製貼上,是
提取共用函式,兩個入口同源呼叫,第三個入口永遠不會再分岔。

parity

接著是操作者追加的那條:跟 opencode、codex 同場景實跑
判準定在介面級——stdout 衛生、退出碼、旗標語意——不比模型智力
(我的後端是本地 7B,對面是旗艦訂閱,比智力既不公平也比不出 CLI 的差距)。

開跑時 spectyn 五格髒,全部同一根因:7B 模型把工具呼叫當文字吐,
CLI 沒兜住,殘渣洩進 stdout。修:strip_tool_residue(紅測四發先行,
含「使用者要的普通 JSON 不准剝」),token 不再流式裸出,回合結束
統一剝離後一次印。修後 echo "5+7=?" | spectyn exec | od -c
恰好是 1 2 \n

外加兩刀反超:寫檔核准(我們擋+訊息+exit 誠實,對面兩家預設直寫)、
裸字誤入(我們 exit 2 + 教路,codex 報錯無建議、opencode 報錯卻 exit 0)。
對手的弱點如實在案——不是為了嘲笑,是為了知道「同級」的真實含義。

四、R3:所謂「雙迴圈合一」,其實是一場葬禮

zombie

R3.1 原案是「把兩套串流迴圈合一」,聽起來像整合工程。動手前先數:
stream_agent_full 在全 repo 零生產呼叫者——只有它自己、兩條
化石註解(「serve/partner 用」,早遷走沒刪)、和兩個測試檔在養它。

所謂兩套迴圈,實況是一套活的 + 一套殭屍。合併=把殭屍 800 行
逐段搬進活體,風險高收益零;正解是退役

過程翻車兩次:手寫的 brace 配平被字串字面值裡的大括號毒死——
一次把檔案砍到 77 行,一次摘一個函式吃掉兩千行。git 是安全網,
兩次都 checkout 重來。第三次改用保留式重建(活體符號都在前 716 行,
新檔 = 頭 + 墓誌銘 + 尾),一次成型。

殭屍的 80 條測試逐類審過才准陪葬——「URL 路由」那批藏著一個
mistral 401 的修痕,查證活體 resolver 已有等價路由表與測試,才放行。

後話:當天稍晚跑全量 selftest 時,編譯器警告揪出葬禮漏了十二具
——保留式重建保住前 716 行時,連死迴圈的常數和整組 pre-stream retry
家族一起保住了。清完之後 streaming.rs 3722 → 715 行、警告歸零
教訓:保留式重建保住的是「安全」,不是「乾淨」;驗收標準不是
「編得過」,是「編譯器沒話說」。

R3.2 順手做了斜線指令表單源(registry + 雙向投影 ratchet)。
對帳測試上線第一分鐘就抓到:TUI 清單比我手抄的多十條,以及
/resume 在 TUI 清單裡宣告了兩次——重複宣告躺了不知多久,
沒有任何機制會發現它,直到有一張表逼兩邊對帳。

五、R4:ensemble 三軌併一,與 crew 的五輪

crew

「ensemble」在 repo 裡指三個東西:in-repo crew 編排、
evolve --judge --ensemble 的多評審、以及 serve 還在 shell-out 的
外部 ensemble binary。裁定:crew 是正宗,外部 binary 退場
(serve 切到 in-repo crew,SSE 表面不變、不再自動 merge),
多評審改稱 judge panel、去留列為操作者裁定點。

然後是實戰。spectyn crew 帶領 codex 實作、claude review、agy audit
——跑了五輪,五種不同的失敗:

  1. agy 缺席:headless 下工具被 auto-deny,兩輪零產出 → quorum 1/2
  2. 視野分裂:worktree 開在 repo 內,審查者往上一層看到原樹,判「改錯目錄」
  3. 落地但空手:LANDED 了,分支上卻只有 init——conductor 改檔不 commit
  4. 卡死:外部 CLI 長尾無回應,逾 40 分鐘終止
  5. 一輪成交:LANDED,分支帶著 range(1, n + 1) 的 crew commit,
    main 未動,分支上跑 pytest 過

每一輪的失敗都不同,每一種都變成了修復(agy 權限旗標、CLI 接
worktree、worktree 移出 repo、remove 前自動 commit)。而
distinct-vendor gate 五輪裡三次說「不」,說得全對——它抓到的
正是「審查者缺席不能算數」。

被帶領同步實測:claude code 經 spectyn mcp 驅動,61 個工具、
file_read/glob 正確,而且零設定沙盒的 Observe 唯讀夾制在 MCP
情境同樣執法
——安全故事跨 CLI、daemon、MCP 三介面一致。

六、R5/R6:app 雙介面,與住進 mesh 的終端

app 的三頁殼(Spectyn | Mesh | Setting)早就上線,所以 R5 的工作
不是蓋骨架,是把 Mesh 頁的空殼補實。雙路掃描(頁面 invoke 清單
× daemon API 對照表)後,15 個指令全數判決:4 個接真行為、
9 個明示桌面版限定(誠實的「需要 desktop app」取代
「[tauri-compat] Unknown command」)、2 個本來就對。Spectyn 頁
18 個指令同法掃畢。

meshterm

R6 最讓我意外:基建全都在。daemon 早有 /ws/pty 的 PTY-over-
WebSocket 橋,/console 早是一個 vendored xterm.js 的完整終端。
差的只有兩件事:PTY 白名單沒有 spectyn(兩行),以及 app 沒接。

spike 實證:/ws/pty?cmd=spectyn101 Switching Protocols
PTY 首批 1038 bytes 流回 spectyn 的開場畫面。整鏈活了。
插曲一則:spike 的 serve 第一次拒絕啟動——因為沙盒 host 解析到
tailnet 位址,而 Day 04 立的「未收斂路由就拒 bind 非 loopback」正在
執法。自家的門神攔了自家的測試,是設計在工作,不是 bug。

接線完成:console 加 spectyn 選項與 URL 預設,app 的 Mesh 分頁多一個
「Spectyn 終端」視圖,e2e 釘住。BIG-GOAL 那句「spectyn cli 要能在
mesh 介面裡以 terminal ui 的方式運作」,落地。

七、誠實清單:綠燈之外的東西

honest

如果只寫成功的部分,這篇會短很多,也假很多。當天的完整帳:

我猜錯的三次:app 測試全紅以為 jsdom 沒裝(否證)、以為
environmentMatchGlobs 吃掉了預設環境(否證)、以為三條 mcp 測試
stdin(null)(否證)。三次都是靠列出所有全域
量二十次啟動耗時這種笨方法找到真相的。

我誤判的兩次:cargo test 0% CPU 就判它掛死——第一次確實該修
(selftest 真的沒有時間上限),第二次純粹是我沒量過基數
(149 個整合測試檔 × 40-60 秒連結,慢是基數不是故障)。

我自己種的債:修 codex 指出的「Mesh 終端硬編埠」時,我加了
第三個 daemon URL 真相源——反覆修「一張表多個 renderer」的人,
自己在補丁裡種了新的分裂。

我破的紀律:第二輪殭屍清掃後,我在 lib 測試驗證回傳前就 push 了
(輸出被 push 訊息蓋掉,我沒回頭確認),而那次剛好砍到了
#[cfg(test)] 裡還在用的 helper。自己定的規矩第一條就是
「push 前先等測試綠」。破了,記在這裡。

外部抓到的:codex 終審三條抽查,兩條真問題(續接歷史存了未剝離
的殘渣、Mesh 終端硬編埠);agy 的可用性面板抓到 trust add 寫進
操作者真家目錄
——trust store 是 #322 資料根統一的最後一個漏網者。

測試自己壞的:selftest 的 cargo test 沒有時間上限(掛住就永遠
掛住);cfg2b 三條的 5 秒預算在滿載下太窄(量測:冷啟 0.12 秒,
5 秒看似 40 倍餘裕——直到 149 個測試檔在連結)。失敗的是預算,
不是產品。

收尾:今天帶走的五句話

  1. 動手之前先數自己有什麼——三個「要做的功能」有兩個半早就存在。
  2. 債的名字會說謊——「雙迴圈合一」量完才知道是葬禮;「檔案間互擾」
    查完才知道是預算太窄。
  3. never-used 警告不是刪除許可證——它只說「這個 build 組態用不到」。
  4. 綠的定義是你親眼看到那行 test result: ok,不是你以為它跑過了。
  5. 韌性不是沒有失敗,是失敗看得見——今天所有修復的共同形狀。

三條測試線的一天:core lib 2688(含殭屍)→ 2637 全綠;
app vitest 34 failed → 0 failed / 655 passed;e2e 10 failed →
32/32 雙瀏覽器全綠;golden-cli 89 全過、編譯器警告 0

還欠操作者的:G4 親手複測、judge panel 去留、剩餘 414 處域名裁定、
curl | sh 的最後一步 wrangler login自動化能證明「它會動」,
只有使用者能證明「它好用」。


下半部:當日實錄(前半段原文)

上面是整理版。以下是當天邊做邊寫的實錄——每一次紅測、每一句
外部判詞、數錯又更正的數字,一字未改。本篇收 R0 到 R4;
R5 之後(含守值時段撿到的五顆)見下一篇。

操作者白天定了新的大目標(plan/day05-goal-refactor.md):全生命週期 ×
opencode/codex 對標矩陣 × ensemble 三軌收斂 × app 雙介面 × terminal 內嵌。
四路偵察兵先行(ensemble 原來是三個東西、app 三頁殼已上線、
/ws/pty+xterm 基建已在、生命週期的升級/常駐/解除安裝零驗證),
佇列 R0–R7 定稿。開跑。

R0.1 宣告債:一條線頭拉出十五條債

原目標只是「update 宣告了但不存在」。母規則的作法是先造一個
從 binary 自身取清單的 ratchet(SPECTYN_DUMP_SUBCOMMANDS 印出
宣告表,測試逐條打 --help,單源不漂移)——它一紅,紅出 15 條:

  • updateversion:宣告多年,沒有任何處理常式(程式自己都會說
    「這是 spectyn 自己的缺」)
  • keys / test / permissions / trust / service / snapshot:把求助當
    未知動作
    ——「--help 要活著」是 Day 04 立的 Q1,漏了整整六條
  • auth / coach / data / identity / lang / peer / project:usage 有印,
    exit code 卻是 2——求助是問題,不是用法錯誤

修法:usage 表補 15 條專屬行;update 別名到 self-update、version
併入 --version同一段代碼(不複製格式,零漂移);
auth/provider/project 命名空間加求助分流(尊重 --)。

兜底的教訓:golden 一巴掌打回來

我順手加了一個 runtime 兜底:「任何宣告的子命令,usage 表沒有就給
通用一行」。golden master 立刻紅了 42 項——兜底把 doctor/exec/
selftest 這些自己有完整說明的命令全劫持了,豐富說明退化成一行
空話。撤掉,改成讓 ratchet 測試本身當保險:新命令 help 壞了
測試會紅,強迫作者補表或修 handler——測試當保險,不是 runtime
兜底當保險。

化石快照

golden 剩 17 項不符,審完發現全是舊快照把壞行為凍成了基準:
Unknown service action: '--help'、exit 2,通通被照過相當「正常」。
基線更新,commit 說明。快照測試的陷阱:它凍住的是「當時的樣子」,
不是「對的樣子」——第一次照相前,先問這是不是病。

收帳:ratchet 全綠(61 條宣告全部求助活著)、lib 2706 綠、
help_never_mutates 綠、golden 89 全過。R0.1 ✓


R0.2 self-update:第一次有人真的走完那條路

self-update 的實跑路徑(下載→sha256 驗證→煙測→原子換→重啟服務)
存在了很久,--dry-run 和 --help 有 golden 快照——實跑,零測試
沙盒補上五情境(wiremock 當發佈源、假 binary 是一支印版本的 script、
HOME 指沙盒讓 macOS 的固定安裝目錄跟著進沙盒):

  1. happy path:下載→驗證→煙測→原子換(舊檔活在 .bak、暫存檔消失)✓
  2. 壞 sha → 拒裝、刪下載、現役檔一位元組不動 ✓
  3. sidecar 404 → 拒裝,錯誤訊息說清「未驗證」✓
  4. 純 http 無明確豁免 → 連下載都不發生(wiremock 斷言 0 次命中)✓
  5. --dry-run → 零網路、零檔案變動 ✓

母規則紅示範:臨時把 sha 驗證打瞎 → 情境 2 應聲而紅 → 恢復 → 全綠。
防線是真的,不是裝飾。

附帶抓到改名債第四案

self-update 換完 binary 會探 launchd 服務重啟——探的是
ai.spectynmesh.serve操作者現役的服務是舊名時代裝的
ai.phantommesh.serve
:label 對不上,self-update 成功之後
daemon 繼續跑舊 binary,無聲。修:重啟探測改為雙 label 迴圈
(新名+legacy),兩個都在就都踢。改名債的教訓寫過三次
「不可盲掃」——第四案補上下聯:不掃的債,要在每個消費點
向後相容
。R0.2 ✓


R0.3 service:安裝路徑,壞得比想像徹底

讀 install 實作時撞見的東西,值得原文照登:程式碼替換的佔位符是
__SPECTYN_BIN__,而模板裡寫的是 __PHANTOM_BIN__——替換
根本沒發生。加上模板的 Label 還是舊名 ai.phantommesh.serve
(常數與檔名早改成 spectynmesh)。合起來:現在跑 service install,
會裝出一個「指向字面 PHANTOM_BIN、掛在舊 Label 下」的服務——
既起不來,也被 status / kickstart / uninstall 永遠找不到。

而且不是一個模板:掃 templates/ 全目錄,serve、autoevolve、
ios-rebuild、Linux systemd 四個模板全是滯留戶。改名債第五案——
規則的下聯再次應驗:不掃的債,每個消費點都要有人對帳。

母規則三紅測先行(模板 Label==常數、佔位符==代碼替換集、
templates/ 全掃無 phantom 殘留),加一條:SPECTYN_PORT 進服務
透傳清單——裝在 17878 的服務,重開機後也要在 17878
(detect_listen_port 的讀寫對稱,服務端補齊)。

真機全循環,快進快出

修完實測(隔離埠 17878、新 Label 與現役舊服務不同名,零衝突):

install → launchd state=running (pid 85535) → healthz@17878 ok
status  → registered: yes
uninstall → plist 刪、label 消、17878 關
現役 ai.phantommesh.serve:前後皆 running,7878 ok — 未受一根指頭

這是 service 的 install→status→uninstall 全循環第一次被完整驗證。
之前所有測試都只碰 status。R0.3 ✓

(遺留裁定項:操作者現役服務還在舊 Label 上——要不要遷移到新 Label,
是操作者的決定;uninstall 刻意不碰 legacy。)


R0.4 onboarding:拒絕得誠實,活得健康

headless(stdin 管線)下 onboarding 直接拒絕——而且拒得漂亮:
解釋原因、給三條非互動替代路。goal 原句「headless 啟動+首頁 200」
的假設是錯的,錯得好:fail-closed 本來就該贏過煙測的方便。

真煙測改走 PTY(expect):精靈起在 7879(現役佔 7878,挑埠邏輯
實戰生效)、首頁 HTTP 200 / 5.6KB / 表單關鍵詞九處命中、收掉即關。

附帶補一個 headless 禮貌:SPECTYN_NO_BROWSER=1 抑制自動開瀏覽器
(SSH/CI 下盲彈 GUI 是噪音)。紅測先行——而那次紅測本身在操作者
螢幕彈了一個死連結分頁
:我把「會 spawn 的函式」當紅測載體,
紅測的副作用也是副作用。記過,下不為例。R0.4 ✓

R0.5 全鏈一鏡到底:2 分 40 秒,十站

install → version(當日 HEAD) → set-key(不印值) → exec「鏈通」
→ repl session → serve@18888 healthz ok → service 裝/卸@17878
→ self-update --dry-run → binary 移除 → 現役核對:分毫未動

十站九站乾淨。第五站(repl session)第一發全敗——沙盒只設了
一家供應商,免費層 429 一來就是全軍覆沒
:單供應商鏈沒有退路,
這是真實新使用者的真實風險(對標註:opencode 單供應商 429 同樣
全敗,同級;但這格記進 R2 對標表)。限流過後補驗:記住 R05B、
隔輪召回「B」正確——session 機制清白。

R0 五項全收。 生命週期從「大半環節零驗證」到「每一站都有
今天的實測與測試」:宣告債 15 條、self-update 五情境、service
四模板、onboarding PTY 煙測、全鏈一鏡。


R1 我這一家的判詞:十條劇本,兩條真 bug

三家外部 AI(codex/agy/opencode)在背景各自操作 spectyn;我自己先走
一遍當第四家。判詞表(零設定沙盒、本地 qwen 7B):

# 場景 判定 現場
1 version/求助 順手 版本清楚、help 結構好
2 第一問 礙手 7B 吐工具殘渣,anti-halluc 有響但答案沒出來
3 @file 礙手 讀對檔,7B 回了空 code fence
4 改檔核准 順手 fail-closed,檔案未動
5 session 續聊 壞→修 見下
6 fork 壞→修 見下
7 管線 礙手 stdout 混入殘渣 JSON
8 退出碼 順手 1/0 正確
9 失效訊息 順手 逐家原話+403 原文
10 打錯字 順手 「你是不是要打 sessions?」完美

兩條真 bug:兩個入口,兩種行為

場景 5/6 在零設定下全滅——追下去是入口分岔:exec 沒 config 會
偵測本機 ollama 接上;repl 不會,直接死在「anthropic no key」,
session 沒建立,fork 自然 not found。同一台機器、同一個沙盒、
同一個使用者,exec 答得出來、repl -c 告訴你去設 key。

而且那句死路訊息還在 macOS 上教 PowerShell 語法
([Environment]::SetEnvironmentVariable)。

修:把零設定 fallback 提取成共用函式 zero_config_local_first_toml()
——exec 與 repl 同源呼叫,第三個入口永遠不會再分岔;文案 platform-aware
(unix 教 export)。紅證=修前實錄,綠證=同劇本重演:repl 建 session、
fork --at 2、分岔召回 ZX12,全通。

judgment 的其餘「礙手」多為 7B 天性(CLI 保證等級不保證聰明),
但 stdout 殘渣(場景 7)記入 R2 對標——opencode/codex 的本地模型
體驗如何,對標見真章。


R1 面板回收:三家外部 AI 的判詞,與四刀修復

codex(gpt-5.6)、agy、opencode(big-pickle)各自在沙盒裡真的操作了
十條劇本——codex 甚至自己發現要開 sandbox_workspace_write.network_access
才連得到 ollama。判詞收斂:

三家全中、面板期間已修:repl 零設定不 fallback(codex 兩條「壞」、
opencode「壞」)、PowerShell 文案(agy/opencode 都點名,agy 還抓到
OPENCODE_API_KEY 舊名殘留)。外部面板與我自測同時抓到同一條 bug,
互為印證——這正是四家並測的意義。

本輪新修四刀:

  1. denied 之後 exit 0(codex S4/S8、opencode S4 齊聲)——非互動下
    工具全被拒、什麼都沒做,卻回成功。codex 的原話狠:「用 0 把實際
    失敗偽裝成成功」。修:全拒且零成功 → exit 1 + 雙語說明教三條路。
    紅測:wiremock 兩輪(tool call→denied→final),修前 exit 0 紅證在案。
  2. [groq] [groq] 前綴口吃(agy)——內層錯誤已帶標籤,外層再包。
    tag_provider 去重,五處呼叫點統一。
  3. 403 指路不準(codex/agy)——groq 的 403 原文說「查網路設定」,
    我們引原話沒錯,但補上自己的注解:401/403 也可能是 key 無效,
    provider list 查狀態。
  4. (前輪)零設定分岔收斂 + platform 文案。

列債待議:stdout 殘渣汙染管線(三家都嫌,--quiet 可濾但預設髒;
記入 R2 對標看 codex/opencode 本地模型的同題表現)、非近似裸字默默
當 prompt(agy/codex;行為變更需對標佐證)、--help 非 TTY 的 stty
雜訊(codex)、trust 兩段式提示與截斷(agy)。

R1 三項全勾:四家判詞、歸類、真 bug 六條全修或列債。


R2 對標矩陣:十二格三家實跑,終表

三個測試員各自在沙盒把 spectyn、opencode 1.17、codex 0.149 的
十二個介面場景真的跑了一輪(od 級驗證,不憑印象)。codex 是
stdout 衛生的黃金標準
(12 格近乎全綠);spectyn 開跑時五格髒
——全部同一根因:7B 模型把工具呼叫當文字吐,CLI 沒兜住,
殘渣洩進 stdout

修:strip_tool_residue,codex 級 stdout

紅測四發先行(fenced 塊剝、bare 塊剝、截斷塊剝、使用者要的
普通 JSON 不准剝
),實作無依賴的殘渣剝離器接進 exec 收尾:
token 不再流式裸出 stdout,回合結束統一剝離後一次印——
echo "5+7=?" | spectyn exec | od -c 現在恰好是 1 2 \n
進度感由 stderr 的 [tool]/[done] 流維持,codex 同款。

補刀兩發

  • M6 形狀:模型只吐殘渣、零工具執行、剝完為空=零交付——
    以前 exit 0 說謊,現在 exit 1 + 雙語明說。
  • M8 裸字:spectyn nosuchcmd 以前默默當 prompt 燒一輪推理
    (三家測試員都嫌),現在單一命令狀裸字 exit 2 + 教路
    (spectyn -c "…" 明示才走 prompt;中文/帶空格的自然語句
    照常是 prompt——那是特性)。

終表(介面級)

判定
M1 版本 / M2 一次答 / M7 session / M9 退出碼 / M11 JSON / M12 cwd 三家同級
M3 管線 同級;opencode 非 TTY 不接 stdin 會掛死 180s,我們不會
M4 分流 / M5 檔案 修後同級(od 級乾淨)
M6 寫檔核准 spectyn 優:我們擋+訊息+exit 誠實;codex 預設 approval never 直寫,opencode 未加 --auto 也直寫零核准
M8 裸字 spectyn 修後最佳:exit 2+建議;codex 非 TTY 報錯無建議,opencode 報錯卻 exit 0
M10 求助 spectyn/codex 同級;opencode 的 help 整包走 stderr,管線拿不到

收夜判定:介面級十二格,spectyn 修後零缺失格、兩格佔優。
模型智力(答案品質)誠實除外——那是供應商的事,矩陣開頭就寫明了。

小債新記:repl -c 的 one-shot 輸出還沒接剝離器(exec 已接),
同款接線列下一輪。附:對手的掛死/stderr-help/零核准都記錄在案,
不是為了嘲笑,是為了知道「同級」的真實含義。


R3 開場:殭屍的發現

R3.1 原案「雙串流迴圈合一」——大手術,先寫設計。偵察的第一刀
就改寫了手術方案:stream_agent_full 在全 repo 零生產呼叫者。
只有 streaming.rs 自身、agent.rs 兩條化石註解(「serve/partner 用」
——早遷走了沒刪),和兩個測試檔在養它。

所謂「兩套迴圈」,實況是一套活的(agent.rs,daemon+CLI 實證,
本週所有修復都在裡面)+ 一套殭屍
。合併=把殭屍 800 行逐段搬進
活體,風險高收益零;正解是退役:殭屍迴圈刪除、streaming.rs
縮身為串流工具箱(salvage/strip/trait——這些是真被用的)、
測試逐條裁決遷移。設計文寫成 plan/R3-雙迴圈收斂設計.md,
已派 codex 複檢(指令:自己 grep 驗證零呼叫者,別信文件)。

教訓预告:債的名字會說謊。「雙迴圈收斂」聽起來像整合工程,
量完才知道是葬禮。動刀前先數屍體,跟動手前先數自己有什麼,
是同一條紀律。

同輪小債清畢:repl -c 接上殘渣剝離器(od 驗證 9\n),
exec 與 repl 兩個 one-shot 面現在同款衛生。


R3.1 殭屍葬禮:3722 → 866 行,一場三幕劇

第一幕,精準摘除。 主家族四口(stream_agent_full 兩兄弟、
stream_agent、stream_one_round)按名字定位、brace 配平摘除,
編譯器接力點名佃農(URL 路由副本、pre-stream 重試、frame 處理器
——每一個都是活體 agent.rs 已有的知識的殭屍副本),迭代清到
never-used 歸零。

第二幕,兩次翻車。 手寫的 brace 配平被字串字面值裡的大括號
騙了兩次——一次把檔案砍到 77 行,一次摘 extract_stream_error 吃掉
兩千行。教訓刻進骨頭:手寫 parser 對 Rust 原始碼不可靠,
配平會被 "{" 字面值毒死
。第三次改用「保留式重建」:活體符號
(salvage、strip、StreamEvent、StreamResult、ResolveProvider)
全在檔案前 716 行,salvage_tests 在尾部——新檔=頭+墓誌銘+尾,
一次成型。git 是安全網:兩次翻車都 checkout 重來,零損失。

第三幕,陪葬審查。 殭屍測試 80 條逐類審過才准死:backoff/
retry_after/frame 處理器測的函式已亡;「URL 路由」那批藏著
mistral 401 的修痕——查活體:resolver.rs 已有等價路由表與
mistral_url_routes_to_native 測試
,陪葬正確。遷移檔
streaming_trait_migration 的 resolver 注入契約,活體
agent_with_resolver.rs 已覆蓋——刪檔。

出口驗收:lib 2631 綠(-80=陪葬數)、七整合檔綠、golden 89
全過、release 部署、daemon 實測 ONE-LOOP-OK
streaming.rs 從 3722 行縮到 866 行,留下的每個符號都有
真 caller。一套迴圈,一個真相源。

(誤殺虛驚一場也記錄:迭代腳本曾把「引用 ProviderEntry 的測試」
連坐摘除——逐條回審,受測物全是亡者,無誤殺;但判準是
「引用了找不到的符號」而非「受測物已亡」,命中靠運氣,記過。)


R3.2 斜線表單源:ratchet 第一戰就立功

cli_slash::SLASH_REGISTRY(name / scope Repl|Tui|Both / usage),
兩介面清單各自成為 registry 的投影,雙向對帳 ratchet 釘死。

對帳測試上線的第一分鐘就抓到兩件事:TUI 實際清單比我手抄的多
十條(/broadcast /fanout /priority /sidebar…),以及——
/resume 在 TUI 清單裡宣告了兩次,重複宣告躺了不知多久,
沒有任何機制會發現它,直到有一張表逼兩邊對帳。

/fork 進 TUI:仿 /resume 的切換機制(fork_at 全量複製→切
chat_id→清 transcript→掛 pending_interrupt 防前 session 的 token
滲流)。REPL 獨有了很久的招牌,現在兩面都有;registry 的
/fork scope=Both + ratchet 保證它再也不會單面消失。

R3.3(命令表審計)判定:宣告=實作=求助已由 R0.1 的
declared_subcommands_help ratchet 覆蓋、文件片段有
documented_scripts_are_runnable、斜線層本輪補齊——三方一致的
主線閉環,usage 欄位補全列為小債。R3 全收。


R4 ensemble 收斂:三軌併一的前半場

R4.1 詞彙裁定(一頁定案):「ensemble」單獨出現=in-repo crew
編排(正宗);多評審機制改稱 judge panel;外部 ensemble binary 退場。

R4.2 serve 切軌:/api/mesh/dev 原本 PATH 探測外部 ensemble
執行檔再 shell-out——現在直接跑 in-repo crew。切軌三原則:
(1) SSE 表面不變(stdout/stderr/exit_code 事件形狀相同,app 的
MeshOps 頁零改動);(2) crew 的 Blackboard 訊息經 RunObserver 流式
進 SSE([role/kind] body);(3) 不再自動 merge——crew 的工作
留在 spectyn-crew/ 分支,落地是人的決定。組裝邏輯同步提取為
crew::assemble_adapters + DEFAULT_CREW_TOML,CLI 與 serve
同源,不存在第三份副本。ratchet:serve.rs 永不得再出現
find_ensemble_exe(源碼掃描測試釘死)。

(範圍註記:serve 裡另有一條「遠端節點 HTTP ensemble 服務」軌
(ensemble_base_url)——那是 mesh 節點間協定,另一場手術,如實保留並記錄。)

R4.3 worktree 隔離:crew::run_in_worktree——git worktree add
臨時分支→conductor 在隔離樹裡跑→呼叫者收成果後 remove,分支保留。
單元測試:worktree 位於 .spectyn-crew/、內容齊、呼叫者工作樹
一位元組不動、移除乾淨。crew 從此不在你的 cwd 裡動手。

lib 2636 綠、golden 89 全過。R4.4 實測(帶領/被帶領)下一輪專場。


R4.4(下):被帶領 —— claude code 直駕 spectyn MCP

我自己開 spectyn mcp 的 stdio,走完整協定:

initialize   → spectyn-mesh 0.6.0
tools/list   → 61 個工具
file_read    → mcp-target: 42        (讀對)
glob_search  → 找到 target.txt       (cwd 感知)
memory_store → [denied] trust enforcement=observe
memory_search→ 誠實回「無此項」

最後兩行是加分題:零設定沙盒的 Observe 唯讀夾制,在 MCP
被帶領情境同樣執法——寫入被拒、後續查詢誠實反映「沒存進去」。
安全故事跨介面一致:CLI、daemon、MCP,同一套信任閘。
「其他 AI 帶領 spectyn」實測成立。


上一篇
Day 04(下):實錄全文 — 紅測、複檢裁決與被拆掉的結論
下一篇
Day 05(下):實錄後半場 — 殭屍葬禮、五輪 crew,與一張誠實清單
系列文
從單一agent 到多agent 集群的開發流水帳以及應用18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言